/*
 * Copyright 2013-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.cloud.gateway.filter.headers;

import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedCaseInsensitiveMap;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;

/**
 *  ForwardedHeadersFilter 的主要作用就是将客户端的真实请求信息（如 IP 地址、协议、主机名等）从反向代理或负载均衡器传递到下游服务。
 *  这个过程确保了后端服务能够正确识别客户端的真实信息，而不会被中间层的代理服务器所掩盖。
 *
 *
 * 用于传递客户端的真实信息（如 IP 地址、协议等），以便后端服务能够正确识别客户端。
 * ForwardedHeadersFilter 的主要目的是解析这些转发头，并将相关信息存储到 Spring 的 ServerWebExchange 对象中，以便下游过滤器或服务能够访问这些信息。
 * 具体功能
 * 		解析转发头
 *			 解析 Forwarded 头或其他类似的头（如 X-Forwarded-For、X-Forwarded-Proto 等）。
 * 			 提取客户端的真实 IP 地址、协议、端口等信息。
 * 		更新请求上下文
 * 			将解析后的信息存储到 ServerWebExchange 的属性中，例如：
 * 			ForwardedRemoteAddress：客户端的真实 IP 地址。
 * 			ForwardedProto：客户端使用的协议。
 * 			ForwardedHost：客户端请求的主机名。
 *
 * 背景
 * 		在现代分布式系统中，反向代理和负载均衡器是常见的架构组件。当请求通过这些中间层时，后端服务看到的请求信息可能被修改了。例如：
 * 			客户端的真实 IP 地址会被代理服务器的 IP 替代。
 * 			HTTPS 请求可能在代理层面被解密，后端服务只看到 HTTP 协议。
 * 			为了解决这些问题，反向代理通常会添加一些标准的 HTTP 头来传递原始信息。Spring Cloud Gateway 的 ForwardedHeadersFilter 就是用来解析这些头，并确保后端服务能够正确使用它们。
 *
 *支持的 HTTP 头
 * 		以下是常见的转发头及其含义：
 * 	1、Forwarded
 * 		标准化的头，用于传递客户端的原始信息。
 * 		示例：
 * 		Forwarded: for=192.0.2.1;proto=https;by=192.0.2.2
 * 			for：客户端的真实 IP 地址。
 * 			proto：客户端使用的协议（如 http 或 https）。
 * 			by：反向代理的地址。
 * 	2、X-Forwarded-For
 * 		非标准化的头，用于传递客户端的真实 IP 地址。
 * 		示例：
 * 		X-Forwarded-For: 192.0.2.1, 198.51.100.1
 * 			第一个值通常是客户端的真实 IP，后续值是经过的代理链。
 * 	3、X-Forwarded-Proto
 * 		非标准化的头，用于传递客户端使用的协议。
 * 		示例：
 * 		X-Forwarded-Proto: https
 *
 * 	4、X-Forwarded-Port
 * 		非标准化的头，用于传递客户端请求的端口号。
 * 		示例：
 * 		X-Forwarded-Port: 443
 * 	5、X-Forwarded-Host
 * 		非标准化的头，用于传递客户端请求的原始主机名。
 * 		示例：
 * 		X-Forwarded-Host: example.com
 *
 */
public class ForwardedHeadersFilter implements HttpHeadersFilter, Ordered {

	/**
	 * Forwarded header.
	 */
	public static final String FORWARDED_HEADER = "Forwarded";

	/* for testing */
	static List<Forwarded> parse(List<String> values) {
		ArrayList<Forwarded> forwardeds = new ArrayList<>();
		if (CollectionUtils.isEmpty(values)) {
			return forwardeds;
		}
		for (String value : values) {
			Forwarded forwarded = parse(value);
			forwardeds.add(forwarded);
		}
		return forwardeds;
	}

	/* for testing */
	static Forwarded parse(String value) {
		String[] pairs = StringUtils.tokenizeToStringArray(value, ";");

		LinkedCaseInsensitiveMap<String> result = splitIntoCaseInsensitiveMap(pairs);
		if (result == null) {
			return null;
		}

		Forwarded forwarded = new Forwarded(result);

		return forwarded;
	}

	/* for testing */ static LinkedCaseInsensitiveMap<String> splitIntoCaseInsensitiveMap(String[] pairs) {
		if (ObjectUtils.isEmpty(pairs)) {
			return null;
		}

		LinkedCaseInsensitiveMap<String> result = new LinkedCaseInsensitiveMap<>();
		for (String element : pairs) {
			String[] splittedElement = StringUtils.split(element, "=");
			if (splittedElement == null) {
				continue;
			}
			result.put(splittedElement[0].trim(), splittedElement[1].trim());
		}
		return result;
	}

	@Override
	public int getOrder() {
		return 0;
	}

	/**
	 * 该函数是Spring Cloud Gateway项目中的一个HTTP头部过滤器方法，主要用于处理Forwarded头部的生成与更新。
	 * 在网关服务中，Forwarded头部用于追踪请求在代理或负载均衡器之间的转发路径，对于理解请求的原始来源、协议及路径至关重要。
	 * 函数通过复制除Forwarded头部外的所有原始头部，然后根据请求信息添加或更新Forwarded头部，最终返回更新后的头部集合。
	 *
	 * 1、代码首先创建了两个HttpHeaders对象，original用于存储传入的原始头部，updated用于构建更新后的头部集合。
	 * 2、接着，通过遍历original中的所有头部条目，排除Forwarded头部后，将其余头部复制到updated中。
	 * 3、随后，代码解析原始Forwarded头部的值，生成Forwarded对象列表。
	 * 		对于列表中的每个Forwarded对象，将其转换为头部值并添加到updated中。
	 * 		这一步骤实现了对原始Forwarded头部的清洗和重组。
	 * 4、接下来，代码根据当前请求的信息构建一个新的Forwarded对象。
	 * 		1）首先，从原始头部中获取Host值，并从请求的URI中获取协议方案（如http或https），将这些信息添加到Forwarded对象中。
	 * 		2）然后，尝试获取请求的远程地址（remoteAddress），并根据远程地址的解析状态，选择使用主机名或IP地址（包括处理IPv6地址的特殊格式）以及端口号，构建for参数的值，并将其添加到Forwarded对象中。
	 * 		3）最后，将新构建的Forwarded对象的头部值添加到updated中，并返回updated作为过滤后的头部集合。
	 *
	 * 此函数在Spring Cloud Gateway项目中扮演了关键角色，通过精确控制Forwarded头部的生成与更新，确保了网关服务能够准确记录和传递请求的转发路径信息。
	 * 这对于实现请求追踪、安全审计、负载均衡及故障排查等功能至关重要。
	 * 在实际业务中，该函数通过与网关服务的其他组件协同工作，为后端服务提供了可靠、准确的请求来源信息，增强了系统的可维护性和安全性。
	 * @param input Http Headers
	 * @param exchange a {@link ServerWebExchange} that should be filtered
	 * @return
	 */
	@Override
	public HttpHeaders filter(HttpHeaders input, ServerWebExchange exchange) {
		ServerHttpRequest request = exchange.getRequest();
		HttpHeaders original = new HttpHeaders();
		original.putAll(input);
		HttpHeaders updated = new HttpHeaders();

		// copy all headers except Forwarded
		for (Map.Entry<String, List<String>> entry : original.entrySet()) {
			if (!entry.getKey().equalsIgnoreCase(FORWARDED_HEADER)) {
				updated.addAll(entry.getKey(), entry.getValue());
			}
		}
		//Forwarded格式例子: Forwarded: for=192.0.2.1;proto=https;by=192.0.2.2
		List<Forwarded> forwardeds = parse(original.get(FORWARDED_HEADER));

		for (Forwarded f : forwardeds) {
			updated.add(FORWARDED_HEADER, f.toHeaderValue());
		}

		// TODO: add new forwarded
		URI uri = request.getURI();
		String host = original.getFirst(HttpHeaders.HOST);
		Forwarded forwarded = new Forwarded().put("host", host).put("proto", uri.getScheme());

		InetSocketAddress remoteAddress = request.getRemoteAddress();
		if (remoteAddress != null) {
			// If remoteAddress is unresolved, calling getHostAddress() would cause a
			// NullPointerException.
			String forValue;
			if (remoteAddress.isUnresolved()) {
				forValue = remoteAddress.getHostName();
			}
			else {
				InetAddress address = remoteAddress.getAddress();
				forValue = remoteAddress.getAddress().getHostAddress();
				if (address instanceof Inet6Address) {
					forValue = "[" + forValue + "]";
				}
			}
			int port = remoteAddress.getPort();
			if (port >= 0) {
				forValue = forValue + ":" + port;
			}
			forwarded.put("for", forValue);
		}
		// TODO: support by?

		updated.add(FORWARDED_HEADER, forwarded.toHeaderValue());

		return updated;
	}

	/* for testing */ static class Forwarded {

		private static final char EQUALS = '=';

		private static final char SEMICOLON = ';';

		private final Map<String, String> values;

		Forwarded() {
			this.values = new HashMap<>();
		}

		Forwarded(Map<String, String> values) {
			this.values = values;
		}

		public Forwarded put(String key, String value) {
			this.values.put(key, quoteIfNeeded(value));
			return this;
		}

		private String quoteIfNeeded(String s) {
			if (s != null && s.contains(":")) { // TODO: broaded quote
				return "\"" + s + "\"";
			}
			return s;
		}

		public String get(String key) {
			return this.values.get(key);
		}

		/* for testing */ Map<String, String> getValues() {
			return this.values;
		}

		@Override
		public String toString() {
			return "Forwarded{" + "values=" + this.values + '}';
		}

		public String toHeaderValue() {
			StringBuilder builder = new StringBuilder();
			for (Map.Entry<String, String> entry : this.values.entrySet()) {
				if (builder.length() > 0) {
					builder.append(SEMICOLON);
				}
				builder.append(entry.getKey()).append(EQUALS).append(entry.getValue());
			}
			return builder.toString();
		}

	}

}
